﻿///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
//
// Type Library Importer utility
//
// This program imports all the types in the type library into a interop assembly
//
///////////////////////////////////////////////////////////////////////////////

namespace tlbimp2.Event
{
    using System.Runtime.InteropServices.ComTypes;
    using ubyte = System.Byte;
    using System;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Collections;
    using System.Threading;
    using System.Diagnostics;

    internal class EventProviderWriter
    {
        private const BindingFlags DefaultLookup = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;

#if USING_NETFX_4
        private readonly Type[] MonitorEnterParamTypes = new Type[] { typeof(Object), Type.GetType("System.Boolean&") };
#endif // !USING_NETFX_4
        
        public EventProviderWriter(ModuleBuilder OutputModule, String strDestTypeName, Type EventItfType, Type SrcItfType, Type SinkHelperType)
        {
            m_OutputModule = OutputModule;
            m_strDestTypeName = strDestTypeName;
            m_EventItfType = EventItfType;
            m_SrcItfType = SrcItfType;
            m_SinkHelperType = SinkHelperType;
        }

        public Type Perform()
        {
            // Create the event provider class.
            TypeBuilder OutputTypeBuilder = m_OutputModule.DefineType(
                m_strDestTypeName,
                TypeAttributes.Sealed | TypeAttributes.NotPublic,
                typeof(Object),
                new Type[] { m_EventItfType, typeof(IDisposable) }
                );

            // Create the event source field.
            FieldBuilder fbCPC = OutputTypeBuilder.DefineField(
                "m_wkConnectionPointContainer",
                typeof(WeakReference),
                FieldAttributes.Private
                );

            // Create array of event sink helpers.
            FieldBuilder fbSinkHelper = OutputTypeBuilder.DefineField(
                "m_aEventSinkHelpers",
                typeof(ArrayList),
                FieldAttributes.Private
                );

            // Define the connection point field.
            FieldBuilder fbEventCP = OutputTypeBuilder.DefineField(
                "m_ConnectionPoint",
                typeof(IConnectionPoint),
                FieldAttributes.Private
                );

            // Define the InitXXX method.
            MethodBuilder InitSrcItfMethodBuilder =
                DefineInitSrcItfMethod(OutputTypeBuilder, m_SrcItfType, fbSinkHelper, fbEventCP, fbCPC);

            // Process all the methods in the event interface.
            MethodInfo[] aMethods = TCEAdapterGenerator.GetNonPropertyMethods(m_SrcItfType);
            for (int cMethods = 0; cMethods < aMethods.Length; cMethods++)
            {
                if (m_SrcItfType == aMethods[cMethods].DeclaringType)
                {
                    // Define the add_XXX method.
                    MethodBuilder AddEventMethodBuilder = DefineAddEventMethod(
                        OutputTypeBuilder, aMethods[cMethods], m_SinkHelperType, fbSinkHelper, fbEventCP, InitSrcItfMethodBuilder);

                    // Define the remove_XXX method.
                    MethodBuilder RemoveEventMethodBuilder = DefineRemoveEventMethod(
                        OutputTypeBuilder, aMethods[cMethods], m_SinkHelperType, fbSinkHelper, fbEventCP);
                }
            }

            // Define the constructor.
            DefineConstructor(OutputTypeBuilder, fbCPC);

            // Define the finalize method.
            MethodBuilder FinalizeMethod = DefineFinalizeMethod(OutputTypeBuilder, m_SinkHelperType, fbSinkHelper, fbEventCP);

            // Define the Dispose method.
            DefineDisposeMethod(OutputTypeBuilder, FinalizeMethod);

            return OutputTypeBuilder.CreateType();
        }

        private MethodBuilder DefineAddEventMethod(TypeBuilder OutputTypeBuilder, MethodInfo SrcItfMethod, Type SinkHelperClass, FieldBuilder fbSinkHelperArray, FieldBuilder fbEventCP, MethodBuilder mbInitSrcItf)
        {
            Type[] aParamTypes;

            // Find the delegate on the event sink helper.
            FieldInfo DelegateField = SinkHelperClass.GetField("m_" + SrcItfMethod.Name + "Delegate");
            Debug.Assert(DelegateField != null, "Unable to find the field m_" + SrcItfMethod.Name + "Delegate on the sink helper");

            // Find the cookie on the event sink helper.
            FieldInfo CookieField = SinkHelperClass.GetField("m_dwCookie");
            Debug.Assert(CookieField != null, "Unable to find the field m_dwCookie on the sink helper");

            // Retrieve the sink helper's constructor.
            ConstructorInfo SinkHelperCons = SinkHelperClass.GetConstructor(EventProviderWriter.DefaultLookup | BindingFlags.NonPublic, null, new Type[0], null);
            Debug.Assert(SinkHelperCons != null, "Unable to find the constructor for the sink helper");

            // Retrieve the IConnectionPoint.Advise method.
            MethodInfo CPAdviseMethod = typeof(IConnectionPoint).GetMethod("Advise");
            Debug.Assert(CPAdviseMethod != null, "Unable to find the method ConnectionPoint.Advise");

            // Retrieve the ArrayList.Add method.
            aParamTypes = new Type[1];
            aParamTypes[0] = typeof(Object);
            MethodInfo ArrayListAddMethod = typeof(ArrayList).GetMethod("Add", aParamTypes, null);
            Debug.Assert(ArrayListAddMethod != null, "Unable to find the method ArrayList.Add");

#if !USING_NETFX_4
            // Retrieve the Monitor.Enter(Object) method.            
            aParamTypes[0] = typeof(Object);
            MethodInfo MonitorEnterMethod = typeof(Monitor).GetMethod("Enter", aParamTypes, null);
#else            
            // Retrieve the Monitor.Enter(Object, ref bool) method.            
            MethodInfo MonitorEnterMethod = typeof(Monitor).GetMethod("Enter", MonitorEnterParamTypes, null);
#endif // !USING_NETFX_4
            Debug.Assert(MonitorEnterMethod != null, "Unable to find the method Monitor.Enter()");

            // Retrieve the Monitor.Exit() method.
            aParamTypes[0] = typeof(Object);
            MethodInfo MonitorExitMethod = typeof(Monitor).GetMethod("Exit", aParamTypes, null);
            Debug.Assert(MonitorExitMethod != null, "Unable to find the method Monitor.Exit()");

            // Define the add_XXX method.
            Type[] parameterTypes;
            parameterTypes = new Type[1];
            parameterTypes[0] = DelegateField.FieldType;
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod(
                "add_" + SrcItfMethod.Name,
                MethodAttributes.Public | MethodAttributes.Virtual,
                null,
                parameterTypes);

            ILGenerator il = Meth.GetILGenerator();

            // Define a label for the m_IFooEventsCP comparision.
            Label EventCPNonNullLabel = il.DefineLabel();

            // Declare the local variables.
            LocalBuilder ltSinkHelper = il.DeclareLocal(SinkHelperClass);
            LocalBuilder ltCookie = il.DeclareLocal(typeof(Int32));

#if !USING_NETFX_4
            // Generate the following code:
            //   Monitor.Enter(this);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorEnterMethod);
#else
            LocalBuilder ltLockTaken = il.DeclareLocal(typeof(bool));
#endif // !USING_NETFX_4

            // Generate the following code:
            //   try {
            il.BeginExceptionBlock();

#if USING_NETFX_4
            // Generate the following code:
            //   Monitor.Enter(this, ref lockTaken);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldloca_S, ltLockTaken);
            il.Emit(OpCodes.Call, MonitorEnterMethod);
#endif // USING_NETFX_4

            // Generate the following code:
            //   if ( m_IFooEventsCP != null ) goto EventCPNonNullLabel;
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbEventCP);
            il.Emit(OpCodes.Brtrue, EventCPNonNullLabel);

            // Generate the following code:
            //   InitIFooEvents();
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, mbInitSrcItf);

            // Mark this as label to jump to if the CP is not null.
            il.MarkLabel(EventCPNonNullLabel);

            // Generate the following code:
            //   IFooEvents_SinkHelper SinkHelper = new IFooEvents_SinkHelper;  
            il.Emit(OpCodes.Newobj, SinkHelperCons);
            il.Emit(OpCodes.Stloc, ltSinkHelper);

            // Generate the following code:
            //   dwCookie = 0;
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Stloc, ltCookie);

            // Generate the following code:
            //   m_IFooEventsCP.Advise( SinkHelper, dwCookie );
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbEventCP);
            il.Emit(OpCodes.Ldloc, ltSinkHelper);
            il.Emit(OpCodes.Castclass, typeof(Object));
            il.Emit(OpCodes.Ldloca, ltCookie);
            il.Emit(OpCodes.Callvirt, CPAdviseMethod);

            // Generate the following code:
            //   SinkHelper.m_dwCookie = dwCookie;
            il.Emit(OpCodes.Ldloc, ltSinkHelper);
            il.Emit(OpCodes.Ldloc, ltCookie);
            il.Emit(OpCodes.Stfld, CookieField);

            // Generate the following code:
            //   SinkHelper.m_FooDelegate = d;
            il.Emit(OpCodes.Ldloc, ltSinkHelper);
            il.Emit(OpCodes.Ldarg, (short)1);
            il.Emit(OpCodes.Stfld, DelegateField);

            // Generate the following code:
            //   m_aIFooEventsHelpers.Add( SinkHelper );
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbSinkHelperArray);
            il.Emit(OpCodes.Ldloc, ltSinkHelper);
            il.Emit(OpCodes.Castclass, typeof(Object));
            il.Emit(OpCodes.Callvirt, ArrayListAddMethod);
            il.Emit(OpCodes.Pop);

            // Generate the following code:
            //   } finally {
            il.BeginFinallyBlock();

#if !USING_NETFX_4
            // Generate the following code:
            //   Monitor.Exit(this);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorExitMethod);
#else
            // Generate the following code:
            //   if (lockTaken)
            //      Monitor.Exit(this);
            Label skipExit = il.DefineLabel();
            il.Emit(OpCodes.Ldloc, ltLockTaken);
            il.Emit(OpCodes.Brfalse_S, skipExit);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorExitMethod);
            il.MarkLabel(skipExit);
#endif // !USING_NETFX_4

            // Generate the following code:
            //   }
            il.EndExceptionBlock();

            // Generate the return opcode.
            il.Emit(OpCodes.Ret);

            return Meth;
        }

        private MethodBuilder DefineRemoveEventMethod(TypeBuilder OutputTypeBuilder, MethodInfo SrcItfMethod, Type SinkHelperClass, FieldBuilder fbSinkHelperArray, FieldBuilder fbEventCP)
        {
            Type[] aParamTypes;

            // Find the delegate on the event sink helper.
            FieldInfo DelegateField = SinkHelperClass.GetField("m_" + SrcItfMethod.Name + "Delegate");
            Debug.Assert(DelegateField != null, "Unable to find the field m_" + SrcItfMethod.Name + "Delegate on the sink helper");

            // Find the cookie on the event sink helper.
            FieldInfo CookieField = SinkHelperClass.GetField("m_dwCookie");
            Debug.Assert(CookieField != null, "Unable to find the field m_dwCookie on the sink helper");

            // Retrieve the ArrayList.RemoveAt method.
            aParamTypes = new Type[1];
            aParamTypes[0] = typeof(Int32);
            MethodInfo ArrayListRemoveMethod = typeof(ArrayList).GetMethod("RemoveAt", aParamTypes, null);
            Debug.Assert(ArrayListRemoveMethod != null, "Unable to find the method ArrayList.RemoveAt()");

            // Retrieve the ArrayList.Item property get method.
            PropertyInfo ArrayListItemProperty = typeof(ArrayList).GetProperty("Item");
            Debug.Assert(ArrayListItemProperty != null, "Unable to find the property ArrayList.Item");
            MethodInfo ArrayListItemGetMethod = ArrayListItemProperty.GetGetMethod();
            Debug.Assert(ArrayListItemGetMethod != null, "Unable to find the get method for property ArrayList.Item");

            // Retrieve the ArrayList.Count property get method.
            PropertyInfo ArrayListSizeProperty = typeof(ArrayList).GetProperty("Count");
            Debug.Assert(ArrayListSizeProperty != null, "Unable to find the property ArrayList.Count");
            MethodInfo ArrayListSizeGetMethod = ArrayListSizeProperty.GetGetMethod();
            Debug.Assert(ArrayListSizeGetMethod != null, "Unable to find the get method for property ArrayList.Count");

            // Retrieve the Delegate.Equals() method.
            aParamTypes[0] = typeof(Delegate);
            MethodInfo DelegateEqualsMethod = typeof(Delegate).GetMethod("Equals", aParamTypes, null);
            Debug.Assert(DelegateEqualsMethod != null, "Unable to find the method Delegate.Equlals()");

#if !USING_NETFX_4
            // Retrieve the Monitor.Enter(Object) method.
            aParamTypes[0] = typeof(Object);
            MethodInfo MonitorEnterMethod = typeof(Monitor).GetMethod("Enter", aParamTypes, null);
#else
            // Retrieve the Monitor.Enter(Object, ref bool) method.
            MethodInfo MonitorEnterMethod = typeof(Monitor).GetMethod("Enter", MonitorEnterParamTypes, null);
#endif // !USING_NETFX_4

            Debug.Assert(MonitorEnterMethod != null, "Unable to find the method Monitor.Enter()");

            // Retrieve the Monitor.Exit() method.
            aParamTypes[0] = typeof(Object);
            MethodInfo MonitorExitMethod = typeof(Monitor).GetMethod("Exit", aParamTypes, null);
            Debug.Assert(MonitorExitMethod != null, "Unable to find the method Monitor.Exit()");

            // Retrieve the ConnectionPoint.Unadvise() method.
            MethodInfo CPUnadviseMethod = typeof(IConnectionPoint).GetMethod("Unadvise");
            Debug.Assert(CPUnadviseMethod != null, "Unable to find the method ConnectionPoint.Unadvise()");

            // Retrieve the Marshal.ReleaseComObject() method.
            MethodInfo ReleaseComObjectMethod = typeof(System.Runtime.InteropServices.Marshal).GetMethod("ReleaseComObject");
            Debug.Assert(ReleaseComObjectMethod != null, "Unable to find the method Marshal.ReleaseComObject()");

            // Define the remove_XXX method.
            Type[] parameterTypes;
            parameterTypes = new Type[1];
            parameterTypes[0] = DelegateField.FieldType;
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod(
                "remove_" + SrcItfMethod.Name,
                MethodAttributes.Public | MethodAttributes.Virtual,
                null,
                parameterTypes);

            ILGenerator il = Meth.GetILGenerator();

            // Declare the local variables.
            LocalBuilder ltNumSinkHelpers = il.DeclareLocal(typeof(Int32));
            LocalBuilder ltSinkHelperCounter = il.DeclareLocal(typeof(Int32));
            LocalBuilder ltCurrSinkHelper = il.DeclareLocal(SinkHelperClass);
#if USING_NETFX_4
            LocalBuilder ltLockTaken = il.DeclareLocal(typeof(bool));
#endif // USING_NETFX_4
 

            // Generate the labels for the for loop.
            Label ForBeginLabel = il.DefineLabel();
            Label ForEndLabel = il.DefineLabel();
            Label FalseIfLabel = il.DefineLabel();
            Label MonitorExitLabel = il.DefineLabel();

#if !USING_NETFX_4
            // Generate the following code:
            //   Monitor.Enter(this);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorEnterMethod);
#endif // !USING_NETFX_4

            // Generate the following code:
            //   try {
            il.BeginExceptionBlock();

#if USING_NETFX_4
            // Generate the following code:
            //   Monitor.Enter(this, ref lockTaken);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldloca_S, ltLockTaken);
            il.Emit(OpCodes.Call, MonitorEnterMethod);
#endif // USING_NETFX_4
 
            // Generate the following code:
            //   if ( m_aIFooEventsHelpers == null ) goto ForEndLabel;		
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbSinkHelperArray);
            il.Emit(OpCodes.Brfalse, ForEndLabel);

            // Generate the following code:
            //   int NumEventHelpers = m_aIFooEventsHelpers.Count;
            //   int cEventHelpers = 0;
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbSinkHelperArray);
            il.Emit(OpCodes.Callvirt, ArrayListSizeGetMethod);
            il.Emit(OpCodes.Stloc, ltNumSinkHelpers);
            il.Emit(OpCodes.Ldc_I4, 0);
            il.Emit(OpCodes.Stloc, ltSinkHelperCounter);

            // Generate the following code:
            //   if ( 0 >= NumEventHelpers ) goto ForEndLabel;		
            il.Emit(OpCodes.Ldc_I4, 0);
            il.Emit(OpCodes.Ldloc, ltNumSinkHelpers);
            il.Emit(OpCodes.Bge, ForEndLabel);

            // Mark this as the beginning of the for loop's body.
            il.MarkLabel(ForBeginLabel);

            // Generate the following code:
            //   CurrentHelper = (IFooEvents_SinkHelper)m_aIFooEventsHelpers.Get( cEventHelpers );
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbSinkHelperArray);
            il.Emit(OpCodes.Ldloc, ltSinkHelperCounter);
            il.Emit(OpCodes.Callvirt, ArrayListItemGetMethod);
            il.Emit(OpCodes.Castclass, SinkHelperClass);
            il.Emit(OpCodes.Stloc, ltCurrSinkHelper);

            // Generate the following code:
            //   if ( CurrentHelper.m_FooDelegate )
            il.Emit(OpCodes.Ldloc, ltCurrSinkHelper);
            il.Emit(OpCodes.Ldfld, DelegateField);
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Beq, FalseIfLabel);

            // Generate the following code:
            //   if ( CurrentHelper.m_FooDelegate.Equals( d ) )
            il.Emit(OpCodes.Ldloc, ltCurrSinkHelper);
            il.Emit(OpCodes.Ldfld, DelegateField);
            il.Emit(OpCodes.Ldarg, (short)1);
            il.Emit(OpCodes.Castclass, typeof(Object));
            il.Emit(OpCodes.Callvirt, DelegateEqualsMethod);
            il.Emit(OpCodes.Ldc_I4, 0xff);
            il.Emit(OpCodes.And);
            il.Emit(OpCodes.Ldc_I4, 0);
            il.Emit(OpCodes.Beq, FalseIfLabel);

            // Generate the following code:
            //   m_aIFooEventsHelpers.RemoveAt( cEventHelpers );
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbSinkHelperArray);
            il.Emit(OpCodes.Ldloc, ltSinkHelperCounter);
            il.Emit(OpCodes.Callvirt, ArrayListRemoveMethod);

            // Generate the following code:
            //   m_IFooEventsCP.Unadvise( CurrentHelper.m_dwCookie );
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbEventCP);
            il.Emit(OpCodes.Ldloc, ltCurrSinkHelper);
            il.Emit(OpCodes.Ldfld, CookieField);
            il.Emit(OpCodes.Callvirt, CPUnadviseMethod);

            // Generate the following code:
            //   if ( NumEventHelpers > 1) break;
            il.Emit(OpCodes.Ldloc, ltNumSinkHelpers);
            il.Emit(OpCodes.Ldc_I4, 1);
            il.Emit(OpCodes.Bgt, ForEndLabel);

            // Generate the following code:
            //   Marshal.ReleaseComObject(m_IFooEventsCP);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbEventCP);
            il.Emit(OpCodes.Call, ReleaseComObjectMethod);
            il.Emit(OpCodes.Pop);

            // Generate the following code:
            //   m_IFooEventsCP = null;
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Stfld, fbEventCP);

            // Generate the following code:
            //   m_aIFooEventsHelpers = null;      
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Stfld, fbSinkHelperArray);

            // Generate the following code:
            //   break;
            il.Emit(OpCodes.Br, ForEndLabel);

            // Mark this as the label to jump to when the if statement is false.
            il.MarkLabel(FalseIfLabel);

            // Generate the following code:
            //   cEventHelpers++;
            il.Emit(OpCodes.Ldloc, ltSinkHelperCounter);
            il.Emit(OpCodes.Ldc_I4, 1);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Stloc, ltSinkHelperCounter);

            // Generate the following code:
            //   if ( cEventHelpers < NumEventHelpers ) goto ForBeginLabel;
            il.Emit(OpCodes.Ldloc, ltSinkHelperCounter);
            il.Emit(OpCodes.Ldloc, ltNumSinkHelpers);
            il.Emit(OpCodes.Blt, ForBeginLabel);

            // Mark this as the end of the for loop's body.
            il.MarkLabel(ForEndLabel);

            // Generate the following code:
            //   } finally {
            il.BeginFinallyBlock();

#if !USING_NETFX_4
            // Generate the following code:
            //   Monitor.Exit(this);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorExitMethod);
#else
            // Generate the following code:
            //   if (lockTaken)
            //      Monitor.Exit(this);
            Label skipExit = il.DefineLabel();
            il.Emit(OpCodes.Ldloc, ltLockTaken);
            il.Emit(OpCodes.Brfalse_S, skipExit);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorExitMethod);
            il.MarkLabel(skipExit);
#endif // !USING_NETFX_4

            // Generate the following code:
            //   }
            il.EndExceptionBlock();

            // Generate the return opcode.
            il.Emit(OpCodes.Ret);

            return Meth;
        }

        private MethodBuilder DefineInitSrcItfMethod(TypeBuilder OutputTypeBuilder, Type SourceInterface, FieldBuilder fbSinkHelperArray, FieldBuilder fbEventCP, FieldBuilder fbCPC)
        {
            // Retrieve the constructor info for the array list's default constructor.
            ConstructorInfo DefaultArrayListCons = typeof(ArrayList).GetConstructor(EventProviderWriter.DefaultLookup, null, new Type[0], null);
            Debug.Assert(DefaultArrayListCons != null, "Unable to find the constructor for class ArrayList");

            // Temp byte array for Guid
            ubyte[] rgByteGuid = new ubyte[16];

            // Retrieve the constructor info for the Guid constructor.
            Type[] aParamTypes = new Type[1];
            aParamTypes[0] = typeof(Byte[]);
            ConstructorInfo ByteArrayGUIDCons = typeof(Guid).GetConstructor(EventProviderWriter.DefaultLookup, null, aParamTypes, null);
            Debug.Assert(ByteArrayGUIDCons != null, "Unable to find the constructor for GUID that accepts a string as argument");

            // Retrieve the IConnectionPointContainer.FindConnectionPoint() method.
            MethodInfo CPCFindCPMethod = typeof(IConnectionPointContainer).GetMethod("FindConnectionPoint");
            Debug.Assert(CPCFindCPMethod != null, "Unable to find the method ConnectionPointContainer.FindConnectionPoint()");

            // Retrieve the WeakReference.Target method.
            MethodInfo WRget_Target = typeof(WeakReference).GetMethod("get_Target");
            Debug.Assert(WRget_Target != null, "Unable to find the property WeakReference.Target");

            // Define the Init method itself.
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod(
                "Init",
                MethodAttributes.Private,
                null,
                null);

            ILGenerator il = Meth.GetILGenerator();

            // Declare the local variables.
            LocalBuilder ltCP = il.DeclareLocal(typeof(IConnectionPoint));
            LocalBuilder ltEvGuid = il.DeclareLocal(typeof(Guid));
            LocalBuilder ltByteArrayGuid = il.DeclareLocal(typeof(Byte[]));

            // Generate the following code:
            //   IConnectionPoint CP = NULL;
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Stloc, ltCP);

            // Get unsigned byte array for the GUID of the event interface.
            rgByteGuid = SourceInterface.GUID.ToByteArray();

            // Generate the following code:
            //  ubyte rgByteArray[] = new ubyte [16];
            il.Emit(OpCodes.Ldc_I4, 0x10);
            il.Emit(OpCodes.Newarr, typeof(Byte));
            il.Emit(OpCodes.Stloc, ltByteArrayGuid);

            // Generate the following code:
            //  rgByteArray[i] = rgByteGuid[i];
            for (int i = 0; i < 16; i++)
            {
                il.Emit(OpCodes.Ldloc, ltByteArrayGuid);
                il.Emit(OpCodes.Ldc_I4, i);
                il.Emit(OpCodes.Ldc_I4, (int)(rgByteGuid[i]));
                il.Emit(OpCodes.Stelem_I1);
            }

            // Generate the following code:
            //   EventItfGuid = Guid( ubyte b[] );          
            il.Emit(OpCodes.Ldloca, ltEvGuid);
            il.Emit(OpCodes.Ldloc, ltByteArrayGuid);
            il.Emit(OpCodes.Call, ByteArrayGUIDCons);

            // Generate the following code:
            //   ((IConnectionPointContainer)(m_wkConnectionPointContainer.Target)).FindConnectionPoint( EventItfGuid, CP );
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbCPC);
            il.Emit(OpCodes.Callvirt, WRget_Target);
            il.Emit(OpCodes.Castclass, typeof(IConnectionPointContainer));
            il.Emit(OpCodes.Ldloca, ltEvGuid);
            il.Emit(OpCodes.Ldloca, ltCP);
            il.Emit(OpCodes.Callvirt, CPCFindCPMethod);

            // Generate the following code:
            //   m_ConnectionPoint = (IConnectionPoint)CP;
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldloc, ltCP);
            il.Emit(OpCodes.Castclass, typeof(IConnectionPoint));
            il.Emit(OpCodes.Stfld, fbEventCP);

            // Generate the following code:
            //   m_aEventSinkHelpers = new ArrayList;      
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Newobj, DefaultArrayListCons);
            il.Emit(OpCodes.Stfld, fbSinkHelperArray);

            // Generate the return opcode.
            il.Emit(OpCodes.Ret);

            return Meth;
        }

        private void DefineConstructor(TypeBuilder OutputTypeBuilder, FieldBuilder fbCPC)
        {
            // Retrieve the constructor info for the base class's constructor.
            ConstructorInfo DefaultBaseClsCons = typeof(Object).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null);
            Debug.Assert(DefaultBaseClsCons != null, "Unable to find the object's public default constructor");

            // Retrieve the constructor info for WeakReference::.ctor(object, bool).
            Type[] aParamTypes = new Type[2];
            aParamTypes[0] = typeof(object);
            aParamTypes[1] = typeof(bool);
            ConstructorInfo WeakReferenceCons = typeof(WeakReference).GetConstructor(EventProviderWriter.DefaultLookup, null, aParamTypes, null);
            Debug.Assert(WeakReferenceCons != null, "Unable to find the constructor for WeakReference that accepts an object and a bool as arguments");


            // Define the default constructor.
            MethodAttributes ctorAttributes = MethodAttributes.SpecialName | (DefaultBaseClsCons.Attributes & MethodAttributes.MemberAccessMask);
            MethodBuilder Cons = OutputTypeBuilder.DefineMethod(
                ".ctor",
                ctorAttributes,
                null,
                new Type[] { typeof(Object) });

            ILGenerator il = Cons.GetILGenerator();

            // Generate the call to the base class constructor.
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, DefaultBaseClsCons);

            // Generate the following code:
            //   m_wkConnectionPointContainer = new WeakReference((IConnectionPointContainer)EventSource, false);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldarg, (short)1);
            il.Emit(OpCodes.Castclass, typeof(IConnectionPointContainer));
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Newobj, WeakReferenceCons);
            il.Emit(OpCodes.Stfld, fbCPC);

            // Generate the return opcode.
            il.Emit(OpCodes.Ret);
        }

        private MethodBuilder DefineFinalizeMethod(TypeBuilder OutputTypeBuilder, Type SinkHelperClass, FieldBuilder fbSinkHelper, FieldBuilder fbEventCP)
        {
            // Find the cookie on the event sink helper.
            FieldInfo CookieField = SinkHelperClass.GetField("m_dwCookie");
            Debug.Assert(CookieField != null, "Unable to find the field m_dwCookie on the sink helper");

            // Retrieve the ArrayList.Item property get method.
            PropertyInfo ArrayListItemProperty = typeof(ArrayList).GetProperty("Item");
            Debug.Assert(ArrayListItemProperty != null, "Unable to find the property ArrayList.Item");
            MethodInfo ArrayListItemGetMethod = ArrayListItemProperty.GetGetMethod();
            Debug.Assert(ArrayListItemGetMethod != null, "Unable to find the get method for property ArrayList.Item");

            // Retrieve the ArrayList.Count property get method.
            PropertyInfo ArrayListSizeProperty = typeof(ArrayList).GetProperty("Count");
            Debug.Assert(ArrayListSizeProperty != null, "Unable to find the property ArrayList.Count");
            MethodInfo ArrayListSizeGetMethod = ArrayListSizeProperty.GetGetMethod();
            Debug.Assert(ArrayListSizeGetMethod != null, "Unable to find the get method for property ArrayList.Count");

            // Retrieve the ConnectionPoint.Unadvise() method.
            MethodInfo CPUnadviseMethod = typeof(IConnectionPoint).GetMethod("Unadvise");
            Debug.Assert(CPUnadviseMethod != null, "Unable to find the method ConnectionPoint.Unadvise()");

            // Retrieve the Marshal.ReleaseComObject() method.
            MethodInfo ReleaseComObjectMethod = typeof(System.Runtime.InteropServices.Marshal).GetMethod("ReleaseComObject");
            Debug.Assert(ReleaseComObjectMethod != null, "Unable to find the method Marshal.ReleaseComObject()");

#if !USING_NETFX_4
            // Retrieve the Monitor.Enter(Object, ref bool) method.
            Type[] aParamTypes = new Type[] { typeof(Object) };
            MethodInfo MonitorEnterMethod = typeof(Monitor).GetMethod("Enter", aParamTypes, null);
#else
            // Retrieve the Monitor.Enter(Object, ref bool) method.
            MethodInfo MonitorEnterMethod = typeof(Monitor).GetMethod("Enter", MonitorEnterParamTypes, null);
#endif // !USING_NETFX_4
            Debug.Assert(MonitorEnterMethod != null, "Unable to find the method Monitor.Enter()");

            // Retrieve the Monitor.Exit() method.
#if USING_NETFX_4
            Type[] aParamTypes = new Type[] { typeof(Object) };
#endif // USING_NETFX_4

            MethodInfo MonitorExitMethod = typeof(Monitor).GetMethod("Exit", aParamTypes, null);
            Debug.Assert(MonitorExitMethod != null, "Unable to find the method Monitor.Exit()");

            // Define the Finalize method itself.
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod("Finalize", MethodAttributes.Public | MethodAttributes.Virtual, null, null);

            ILGenerator il = Meth.GetILGenerator();

            // Declare the local variables.
            LocalBuilder ltNumSinkHelpers = il.DeclareLocal(typeof(Int32));
            LocalBuilder ltSinkHelperCounter = il.DeclareLocal(typeof(Int32));
            LocalBuilder ltCurrSinkHelper = il.DeclareLocal(SinkHelperClass);
#if !USING_NETFX_4
            // Generate the following code:
            //   Monitor.Enter(this);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorEnterMethod);
#else
            LocalBuilder ltLockTaken = il.DeclareLocal(typeof(bool));
#endif // !USING_NETFX_4

            // Generate the following code:
            //   try {
            il.BeginExceptionBlock();

#if USING_NETFX_4
            // Generate the following code:
            //   Monitor.Enter(this, ref lockTaken);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldloca_S, ltLockTaken);
            il.Emit(OpCodes.Call, MonitorEnterMethod);
#endif // USING_NETFX_4
            // Generate the labels.
            Label ForBeginLabel = il.DefineLabel();
            Label ReleaseComObjectLabel = il.DefineLabel();
            Label AfterReleaseComObjectLabel = il.DefineLabel();

            // Generate the following code:
            //   if ( m_IFooEventsCP == null ) goto AfterReleaseComObjectLabel;		
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbEventCP);
            il.Emit(OpCodes.Brfalse, AfterReleaseComObjectLabel);

            // Generate the following code:
            //   int NumEventHelpers = m_aIFooEventsHelpers.Count;
            //   int cEventHelpers = 0;
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbSinkHelper);
            il.Emit(OpCodes.Callvirt, ArrayListSizeGetMethod);
            il.Emit(OpCodes.Stloc, ltNumSinkHelpers);
            il.Emit(OpCodes.Ldc_I4, 0);
            il.Emit(OpCodes.Stloc, ltSinkHelperCounter);

            // Generate the following code:
            //   if ( 0 >= NumEventHelpers ) goto ReleaseComObjectLabel;		
            il.Emit(OpCodes.Ldc_I4, 0);
            il.Emit(OpCodes.Ldloc, ltNumSinkHelpers);
            il.Emit(OpCodes.Bge, ReleaseComObjectLabel);

            // Mark this as the beginning of the for loop's body.
            il.MarkLabel(ForBeginLabel);

            // Generate the following code:
            //   CurrentHelper = (IFooEvents_SinkHelper)m_aIFooEventsHelpers.Get( cEventHelpers );
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbSinkHelper);
            il.Emit(OpCodes.Ldloc, ltSinkHelperCounter);
            il.Emit(OpCodes.Callvirt, ArrayListItemGetMethod);
            il.Emit(OpCodes.Castclass, SinkHelperClass);
            il.Emit(OpCodes.Stloc, ltCurrSinkHelper);

            // Generate the following code:
            //   m_IFooEventsCP.Unadvise( CurrentHelper.m_dwCookie );
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbEventCP);
            il.Emit(OpCodes.Ldloc, ltCurrSinkHelper);
            il.Emit(OpCodes.Ldfld, CookieField);
            il.Emit(OpCodes.Callvirt, CPUnadviseMethod);

            // Generate the following code:
            //   cEventHelpers++;
            il.Emit(OpCodes.Ldloc, ltSinkHelperCounter);
            il.Emit(OpCodes.Ldc_I4, 1);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Stloc, ltSinkHelperCounter);

            // Generate the following code:
            //   if ( cEventHelpers < NumEventHelpers ) goto ForBeginLabel;
            il.Emit(OpCodes.Ldloc, ltSinkHelperCounter);
            il.Emit(OpCodes.Ldloc, ltNumSinkHelpers);
            il.Emit(OpCodes.Blt, ForBeginLabel);

            // Mark this as the end of the for loop's body.
            il.MarkLabel(ReleaseComObjectLabel);

            // Generate the following code:
            //   Marshal.ReleaseComObject(m_IFooEventsCP);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbEventCP);
            il.Emit(OpCodes.Call, ReleaseComObjectMethod);
            il.Emit(OpCodes.Pop);

            // Mark this as the end of the for loop's body.
            il.MarkLabel(AfterReleaseComObjectLabel);

            // Generate the following code:
            //   } catch {
            il.BeginCatchBlock(typeof(System.Exception));
            il.Emit(OpCodes.Pop);

            // Generate the following code:
            //   } finally {
            il.BeginFinallyBlock();

#if !USING_NETFX_4
            // Generate the following code:
            //   Monitor.Exit(this);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorExitMethod);
#else
            // Generate the following code:
            //   if (lockTaken)
            //      Monitor.Exit(this);
            Label skipExit = il.DefineLabel();
            il.Emit(OpCodes.Ldloc, ltLockTaken);
            il.Emit(OpCodes.Brfalse_S, skipExit);
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, MonitorExitMethod);
            il.MarkLabel(skipExit);
#endif // !USING_NETFX_4

            // Generate the following code:
            //   }
            il.EndExceptionBlock();

            // Generate the return opcode.
            il.Emit(OpCodes.Ret);

            return Meth;
        }

        private void DefineDisposeMethod(TypeBuilder OutputTypeBuilder, MethodBuilder FinalizeMethod)
        {
            // Retrieve the method info for GC.SuppressFinalize().
            MethodInfo SuppressFinalizeMethod = typeof(GC).GetMethod("SuppressFinalize");
            Debug.Assert(SuppressFinalizeMethod != null, "Unable to find the GC.SuppressFinalize");

            // Define the Finalize method itself.
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod("Dispose", MethodAttributes.Public | MethodAttributes.Virtual, null, null);

            ILGenerator il = Meth.GetILGenerator();

            // Generate the following code:
            //   Finalize()
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Callvirt, FinalizeMethod);

            // Generate the following code:
            //   GC.SuppressFinalize()
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, SuppressFinalizeMethod);

            // Generate the return opcode.
            il.Emit(OpCodes.Ret);
        }

        private ModuleBuilder m_OutputModule;
        private String m_strDestTypeName;
        private Type m_EventItfType;
        private Type m_SrcItfType;
        private Type m_SinkHelperType;
    }
}
